/* 
 * File:   Matrix.h
 * Author: gustavo
 *
 * Created on June 9, 2012, 2:17 AM
 */

#ifndef MATRIX_H
#define	MATRIX_H

#include <iostream>

//#include <stdio.h>

#include "../internal/internal.hpp"
#include "../aux/referencable.hpp"
#include "../aux/range.hpp"
#include "../util/msgpp.hpp"

#include "operator.hpp"

namespace imp {
	
	namespace math {

		struct Matx {
			int rows;
			int columns;
			int size;
			int stride;
		};

		template <typename T = imp::internal::DefaultDataType> class Matrix : public Matx, protected Referenceable {
		public: /* Public Attributes */

			T * data;

		public: /* Constructors */
			Matrix();
			Matrix(int rows, int cols);
			Matrix(int rows, int cols, T * data);
			Matrix(int rows, int cols, T * data, int stride);
			Matrix(const Matrix<T>& orig);
			template <typename Ti> Matrix(const Matrix<Ti>& orig);

			Matrix(const Range& rrows, const Range& rcols);
			Matrix(const Matrix<T>& orig, const Range& rrows, const Range& rcols);

			virtual ~Matrix();

		public: /* Methods */

			void refTo(const Matrix<T>& M, int r, int c, int rows, int cols) {
				__release();
				this->data = M.data + (r * M.stride + c);
				this->rows = rows;
				this->columns = cols;
				this->stride = M.stride;
				this->size = rows*cols;

				makeReferenceTo(&M);
			};

			void refTo(const Matrix<T>& M) {
				refTo(M, 0, 0, M.rows, M.columns);
			}

			void refTo(const Matrix<T>& M, int r, int c) {
				refTo(M, r, c, M.rows - r, M.columns - c);
			}

			void set(T val);
			void setData(int rows, int cols, T * data, int stride);
			void setSize(int rows, int cols);
			void clear();
			template <typename Ti> void copy(const Matrix<Ti>& orig);
			void release();
			void print(const char * str = 0) const;

			const Matrix<T> transpose() const;


		public: /* Operators */
			const Matrix<T>& operator =(const Matrix<T>& orig);
			template <typename T2> const Matrix<T>& operator =(const Matrix<T2>& orig);
			const Matrix<T>& operator =(T val);
			template <class Op> const Matrix<T>& operator =(Operator<T, Op> op);
			T& operator ()(int i) const;
			T& operator ()(int row, int col) const;

		private: /* Auxiliary Methods */
			void __clear_attributes();
			void __alloc(int rows, int cols);
			void __release();
			void __copy_data(const Matrix<T>& orig);
			template <typename Ti> void __copy_data(const Matrix<Ti>& orig);

			void __print_head() const;
			void __print_data() const;

		};

		/* = Constructors ======================================================= */
		template <typename T> Matrix<T>::Matrix() {
			__clear_attributes();
		}

		template <typename T> Matrix<T>::Matrix(int rows, int cols) {
			__alloc(rows, cols);
		}

		template <typename T> Matrix<T>::Matrix(int rows, int cols, T * data) {
			setData(rows, cols, data, cols);
		}

		template <typename T> Matrix<T>::Matrix(int rows, int cols, T * data, int stride) {
			setData(rows, cols, data, stride);
		}

		template <typename T> Matrix<T>::Matrix(const Matrix<T>& orig) {
			__alloc(orig.rows, orig.columns);
			__copy_data(orig);
		}

		template <typename T> template <typename Ti> Matrix<T>::Matrix(const Matrix<Ti>& orig) {
			__alloc(orig.rows, orig.columns);
			__copy_data(orig);
		}

		template <typename T> Matrix<T>::Matrix(const Range& rrows, const Range& rcols) {
			__alloc(rrows.size(), rcols.size());
		}

		template <typename T> Matrix<T>::~Matrix() {
			__release();
		}

		/* = Methods ============================================================ */
		template <typename T> void Matrix<T>::set(T val) {

			T * dest_dt = this->data;

			for (int r = 0; r < this->rows; r++) {
				for (int c = 0; c < this->columns; c++) {
					*dest_dt = val;
					dest_dt++;
				}
				dest_dt += (this->stride - this->columns);
			}
		}

		template <typename T> void Matrix<T>::setData(int rows, int cols, T* data, int stride) {

			if (this->data != 0) __release();

			this->rows = rows;
			this->columns = cols;
			this->data = data;
			this->stride = columns;
			this->size = stride;
		}

		template <typename T> void Matrix<T>::setSize(int rows, int cols) {
			__release();
			__alloc(rows, cols);
		}

		template <typename T> void Matrix<T>::clear() {
			this->set(0);
		}

		template <typename T> template <typename Ti> void Matrix<T>::copy(const Matrix<Ti>& orig) {

			if (this->data == 0) {
				__alloc(orig.rows, orig.columns);
			} else {
				__assert_msg(rows == orig.rows && columns == orig.columns, "Matrices of different shapes!");
				//		if ((this->rows != orig.rows) || (this->columns != orig.columns)) {
				//			__error("Matrices of different shapes!");
				//		}
			}
			__copy_data(orig);
		}

		template <class T> void Matrix<T>::release() {
			__release();
			__clear_attributes();
		}

		template <typename T> void Matrix<T>::print(const char * str) const {

			if (str) std::cout << str << std::endl;

			__print_head();
			__print_data();
		}

		template <typename T> const Matrix<T> Matrix<T>::transpose() const {
			Matrix<T> Mt(this->columns, this->rows);
			int m, n;

			for (m = 0; m < this->rows; ++m) {
				for (n = 0; n < this->columns; ++n) {
					Mt(n, m) = this->operator ()(m, n);
				}
			}
			return Mt;

		}


		/* = Operators ========================================================== */
		// inside matrix_op.h

		template <typename T> const Matrix<T>& Matrix<T>::operator =(const Matrix<T>& orig) {
			if (this != &orig) {
				this->copy(orig);
			}
			return *this;
		}

		template <typename T> template <typename T2> const Matrix<T>& Matrix<T>::operator =(const Matrix<T2>& orig) {
			__warning("Is not yet implemented!");
			return *this;
		}

		template <typename T> const Matrix<T>& Matrix<T>::operator =(T val) {
			this->set(val);
			return *this;
		}

		template <typename T> template <class Op> const Matrix<T>& Matrix<T>::operator =(const Operator<T, Op> op) {
			if (this->data == 0) {
				__alloc(op.rows(), op.columns());
			} else {
				__assert_msg(rows == op.rows() && columns == op.columns(), "Matrices of different shapes!");
				//	if ((this->rows != op.rows()) || (this->columns != op.columns())) {
				//		__error("Matrices of different shapes!");
				//	}
			}

			op.eval(*this);
			return *this;
		}

		template <typename T> T& Matrix<T>::operator ()(int i) const {
			return this->data[i];
		}

		template <typename T> T& Matrix<T>::operator ()(int row, int col) const {
			return this->data[col + row * this->stride];
		}

		/* = Private Methods ==================================================== */
		template <typename T> void Matrix<T>::__clear_attributes() {
			rows = 0;
			columns = 0;
			size = 0;
			stride = 0;
			data = 0;
		}

		template <typename T> void Matrix<T>::__alloc(int rows, int cols) {

			__assert_msg(rows > 0 && cols > 0, "Invalid matrix dimensions!");

			//		this->size = rows*cols;
			//		if (this->size < 1) {
			//
			//			__clear_attributes();
			//			return;
			//
			//			//Msg::error("Unable to alloc %d bytes in memory!\n", this->size * sizeof (T));
			//		}

			this->size = rows*cols;
			this->data = new T[this->size];

			__assert_msg(data != 0, "Unable to allocate memory!");

			this->rows = rows;
			this->columns = cols;
			this->stride = cols;

		}

		template <typename T> void Matrix<T>::__release() {
			if (this->data == 0) return;

			if (isRef()) {
				unRef();
				return;
			} else {
				delete [] this->data;
			}

			this->data = 0;
		}

		template <typename T> void Matrix<T>::__copy_data(const Matrix<T>& orig) {
			T * orig_dt = orig.data;
			T * dest_dt = this->data;

			for (int r = 0; r < orig.rows; r++) {
				for (int c = 0; c < orig.columns; c++) {

					*dest_dt = (T) * orig_dt;

					orig_dt++;
					dest_dt++;
				}
				orig_dt += (orig.stride - orig.columns);
				dest_dt += (this->stride - this->columns);
			}
		}

		template <typename T> template <typename Ti> void Matrix<T>::__copy_data(const Matrix<Ti>& orig) {
			Ti * orig_dt = orig.data;
			T * dest_dt = this->data;

			for (int r = 0; r < orig.rows; r++) {
				for (int c = 0; c < orig.columns; c++) {

					*dest_dt = (T) * orig_dt;

					orig_dt++;
					dest_dt++;
				}
				orig_dt += (orig.stride - orig.columns);
				dest_dt += (this->stride - this->columns);
			}
		}

		template <typename T> void Matrix<T>::__print_head() const {
			if (!data) {
				std::cout << "=[Null]===============================" << std::endl << std::endl;
				return;
			}
			std::cout << "=[" << rows << "x" << columns << "]================================" << std::endl;
		}

		template <typename T> void Matrix<T>::__print_data() const {
			int r, c, _r, i;

			for (r = 0, _r = r * stride; r < this->rows; r++, _r += stride) {
				for (c = 0; c < this->columns; c++) {
					i = c + _r;

					//std::cout.precision(8);
					std::cout.width(5);
					//std::cout.setf( std::cout.floatfield );

					std::cout << this->data[i] << " ";

				}
				std::cout.width(0);
				std::cout << std::endl;
			}
			std::cout << std::endl;
		}

		template <> void Matrix<char>::__print_data() const {
			int r, c, _r, i;

			for (r = 0, _r = r * stride; r < this->rows; r++, _r += stride) {
				for (c = 0; c < this->columns; c++) {
					i = c + _r;

					std::cout.precision(3);
					std::cout.width(5);
					//std::cout.setf( std::cout.floatfield );
					std::cout << (int) this->data[i] << " ";
				}
				std::cout.width(0);
				std::cout << std::endl;
			}
			std::cout << std::endl;
		}

		template <> void Matrix<unsigned char>::__print_data() const {
			int r, c, _r, i;

			for (r = 0, _r = r * stride; r < this->rows; r++, _r += stride) {
				for (c = 0; c < this->columns; c++) {
					i = c + _r;

					std::cout.precision(3);
					std::cout.width(5);
					//std::cout.setf( std::cout.floatfield );

					std::cout << (int) this->data[i] << " ";

				}
				std::cout.width(0);
				std::cout << std::endl;
			}
			std::cout << std::endl;
		}

	}

}
#endif	/* MATRIX_H */

